package com.zheng1.envii.btutils

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.content.Context
import android.text.format.DateFormat
import android.util.Log
import com.zheng1.envii.room.Repository.EnvIIBleDeviceRepository
import com.zheng1.envii.room.database.EnvIIBleDeviceDatabase
import com.zheng1.envii.room.entity.EnvIIBleDeviceDataEntity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.sql.Timestamp
import java.text.SimpleDateFormat
import java.time.LocalTime
import java.util.Locale
import java.util.TimeZone
import java.util.UUID
import kotlin.experimental.and
import kotlin.experimental.or

enum class BleDeviceStatus{
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
}

data class EnvIIBleDeviceData(
    val time:String,
    val temperature: String,
    val humidity: String,
    val airpressure: String
)

@SuppressLint("MissingPermission")
class EnvIIBleDevice(
    private val context: Context,
    private val bluetoothDevice: BluetoothDevice,
    private val bluetoothRssi:Int
) {
    private val TAG = "EnvIIBleDevice"
    private var _bleGatt : BluetoothGatt? = null
    val name : String = bluetoothDevice.name ?: ""
    val address : String = bluetoothDevice.address
    val rssi = bluetoothRssi

    private val UUID_SERVICE_ENVII = UUID.fromString( "0000F100-0000-1000-8000-00805F9B34FB")
    private val UUID_CHARACTERISTIC_ENVII = UUID.fromString("0000f101-0000-1000-8000-00805f9b34fb")
    private val UUID_CHARACTERISTICDESC_ENVII = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")

    private var _connectStatus = MutableStateFlow(BleDeviceStatus.DISCONNECTED)
    val connectStatus : Flow<BleDeviceStatus> = _connectStatus

    private var _sensorCurrentData = MutableStateFlow<EnvIIBleDeviceData>(
        EnvIIBleDeviceData( time = "N/A", temperature = "N/A", humidity = "N/A", airpressure = "N/A" )
    )
    val sensorCurrentData : Flow<EnvIIBleDeviceData> = _sensorCurrentData

    private val envIIScope = CoroutineScope( Dispatchers.Main )

    private val envIIBleDeviceDataDao = EnvIIBleDeviceDatabase.getInstance(context).envIIBleDeviceDataDao()
    private val envIIBleDeviceRepository = EnvIIBleDeviceRepository( envIIBleDeviceDataDao )
    val sensorHistoryData = envIIBleDeviceRepository.getDeviceDataByAddress(address).map { data ->
        val dataList = data.map {
            EnvIIBleDeviceData(
                time = SimpleDateFormat("hh:mm:ss", Locale.CHINA).apply {
                    timeZone = TimeZone.getDefault()
                }.format(it.timestamp).toString(),
                temperature = String.format(Locale.US, "%.2f", it.temperature),
                humidity = String.format(Locale.US, "%.2f", it.humidity),
                airpressure = String.format(Locale.US, "%.3f", (it.airpressure)),
            )
        }
        return@map dataList
    }

    fun connect(
        onConnectionStateChange: ( newState : BleDeviceStatus ) -> Unit,
    ){
        bluetoothDevice.connectGatt(context, true, BleGattCallback(onConnectionStateChange))
    }

    fun disConnect(){
        subscribe(false)
        _bleGatt?.disconnect()
    }

    fun subscribe( boolean: Boolean ){
        val notifyValue = when(boolean){
            true->BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            false->BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
        }

        _bleGatt?.let { bleGatt->
            val characteristic = bleGatt.getService(UUID_SERVICE_ENVII)?.getCharacteristic(UUID_CHARACTERISTIC_ENVII)
            val isEnableNotify = bleGatt.setCharacteristicNotification(characteristic, boolean)
            if( isEnableNotify ){
                characteristic?.descriptors?.forEach{ descriptor ->
                    if( descriptor.uuid.equals(UUID_CHARACTERISTICDESC_ENVII) ){
                        descriptor.value = notifyValue
                        bleGatt.writeDescriptor(descriptor)
                    }
                }
            }
        }
    }

    fun read(){
        _bleGatt.let {
            val chardDescriptor = it?.getService(UUID_SERVICE_ENVII)?.getCharacteristic(UUID_CHARACTERISTIC_ENVII)
            it?.readCharacteristic(chardDescriptor)
        }
    }

    fun clearHistoryData(){
        envIIScope.launch {
            envIIBleDeviceRepository.delete(address)
        }
    }

    private fun handleSensorDataNotify(value: ByteArray){
        val temperature = (value[0].toInt() and 0xff shl 8  ) or  (value[1].toInt() and  0xff)
        val humidity = (value[2].toInt() and 0xff shl 8 ) or (value[3].toInt() and  0xff)
        val airpressure = (value[4].toInt() and 0xff shl 24 ) or
                (value[5].toInt() and  0xff shl 16) or
                (value[6].toInt() and  0xff shl 8) or
                (value[7].toInt() and  0xff)

        val temperatureFloat = temperature / 100.0f
        val humidityFloat = humidity / 100.0f
        val airpressureFloat = airpressure / 100000.0f
        envIIScope.launch {
            val time=System.currentTimeMillis()
            val formatTime : CharSequence= SimpleDateFormat("hh:mm:ss", Locale.getDefault()).apply {
                timeZone = TimeZone.getDefault()
            }.format(time)

            _sensorCurrentData.emit(
                EnvIIBleDeviceData(
                    time = formatTime.toString(),
                    temperature = String.format(Locale.US, "%.2f", temperatureFloat),
                    humidity = String.format(Locale.US, "%.2f", humidityFloat),
                    airpressure = String.format(Locale.US, "%.3f", airpressureFloat),
                )
            )
            envIIBleDeviceRepository.insert(
                EnvIIBleDeviceDataEntity(
                    id = 0,
                    timestamp = time,
                    deviceName = name,
                    deviceAddress = address,
                    temperature = temperatureFloat,
                    humidity = humidityFloat,
                    airpressure = airpressureFloat,
                )
            )
        }
    }

    inner class BleGattCallback(private val onConnectionStateChangeCb: ( newState : BleDeviceStatus) -> Unit) : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            when (newState) {
                BluetoothGatt.STATE_CONNECTED -> {
                    gatt?.discoverServices()
                }
                BluetoothGatt.STATE_DISCONNECTED -> {
                    _bleGatt = null
                    _connectStatus.value = BleDeviceStatus.DISCONNECTED
                    onConnectionStateChangeCb.invoke(BleDeviceStatus.DISCONNECTED)
                }
                BluetoothGatt.STATE_CONNECTING -> {
                    onConnectionStateChangeCb.invoke(BleDeviceStatus.CONNECTING)
                }
                else ->{
                    _bleGatt = null
                }
            }

        }

        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
            _bleGatt = gatt
            if( status == BluetoothGatt.GATT_SUCCESS ) {
                _connectStatus.value = BleDeviceStatus.CONNECTED
                onConnectionStateChangeCb.invoke(BleDeviceStatus.CONNECTED)
                subscribe(true)
            }
        }

        override fun onCharacteristicRead(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            status: Int
        ) {
            Log.e(TAG, "onCharacteristicRead ${status}")
            if( status == BluetoothGatt.GATT_SUCCESS ){
                _sensorCurrentData.value = EnvIIBleDeviceData(time = "N/A", temperature = "27.3", humidity = "55.12", airpressure = "101.123")
            }
        }

        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray
        ) {
            if( characteristic.uuid == UUID_CHARACTERISTIC_ENVII ){
                handleSensorDataNotify(value)
            }
        }

        override fun onDescriptorWrite(
            gatt: BluetoothGatt?,
            descriptor: BluetoothGattDescriptor?,
            status: Int
        ) {
            Log.e(TAG, "onDescriptorWrite ${status}")
        }

    }
}